package xfuzzy.xfghl.codification;

import java.util.List;
import java.util.ArrayList;

import xfuzzy.xfhl.model.hierStruct.XfhlINode;
import xfuzzy.xfhl.model.hierStruct.XfhlModule;
import xfuzzy.xfhl.model.hierStruct.XfhlNode;

/**
 * Clase encargada de la codificacion de estructuras jerarquicas
 * @author Alberto David Fernandez Fernandez
 * */

public class Encoder {

//	private static String codeForward(XfhlModule modulo)  {
//		
//		String codificacion = "";
//		List<String> salida = new ArrayList<String>();
//		List<XfhlModule> modulos = new ArrayList<XfhlModule>();
//		//XfhlModule auxiliar = null;
//		XfhlModule actual = null;
//		modulos.add(modulo);
//		while (modulos.size() > 0)  {
//			actual = modulos.remove(0);
//			//if ((actual.getEnt1() instanceof XfhlModule) || (actual.getEnt2() instanceof XfhlModule))  {
//			salida.add(0, "-");
//			if (actual.getEnt1() instanceof XfhlNode)  {
//				//salida.add(0, String.valueOf(((XfhlNode)auxiliar.getEnt1()).getValor() + 1));
//				salida.add(0, String.valueOf(((XfhlNode)actual.getEnt1()).getValor()));
//			}
//			if (actual.getEnt2() instanceof XfhlNode)  {
//				//salida.add(0, String.valueOf(((XfhlNode)auxiliar.getEnt2()).getValor() + 1));
//				salida.add(0, String.valueOf(((XfhlNode)actual.getEnt2()).getValor()));
//			}
//			if (actual.getEnt1() instanceof XfhlModule)  {
//				//salida.add(0, "-");
//				if (actual.getEnt2() instanceof XfhlModule)
//					modulos.add(0, (XfhlModule)actual.getEnt2());
//				modulos.add(0, (XfhlModule)actual.getEnt1());
//			}
//			else  {
//				if (actual.getEnt2() instanceof XfhlModule)  {
//					//salida.add(0, "-");
//					modulos.add(0, (XfhlModule)actual.getEnt2());
//				}
//			}
//		}
//		for (String s : salida)
//			 codificacion += s;
//		//System.out.println("Codificacion: " + codificacion);
//		return codificacion;
//	}
//	
//	@SuppressWarnings("unused")
//	private static DoubleChromosome codeBackward(XfhlModule modulo, int numVariables)  {
//		
//		List<String> salida = new ArrayList<String>();
//		List<XfhlModule> modulos = new ArrayList<XfhlModule>();
//		XfhlModule actual = null;
//		modulos.add(modulo);
//		while (modulos.size() > 0)  {
//			actual = modulos.remove(0);
//			salida.add("-");
//			if (actual.getEnt1() instanceof XfhlNode)  {
//				salida.add(String.valueOf(((XfhlNode)actual.getEnt1()).getValor()));
//			}
//			if (actual.getEnt2() instanceof XfhlNode)  {
//				salida.add(String.valueOf(((XfhlNode)actual.getEnt2()).getValor()));
//			}
//			if (actual.getEnt1() instanceof XfhlModule)  {
//				if (actual.getEnt2() instanceof XfhlModule)
//					modulos.add(0, (XfhlModule)actual.getEnt2());
//				modulos.add(0, (XfhlModule)actual.getEnt1());
//			}
//			else  {
//				if (actual.getEnt2() instanceof XfhlModule)  {
//					modulos.add(0, (XfhlModule)actual.getEnt2());
//				}
//			}
//		}
//		return new DoubleChromosome(numVariables, salida);
//	}
//	
//	
//	
//	@SuppressWarnings("unused")
//	private static XfhlModule decodificar(String codificacion)  {
//		
//		List<XfhlModule> modulos_pendientes = new ArrayList<XfhlModule>();
//		int var_inner = 1, var_output = 1;
//		//XfhlModule resultado = new XfhlModule();
//		XfhlModule resultado = null;
//		XfhlModule actual = null;
//		XfhlModule siguiente = null;
//		//resultado.setSal(new XfhlNode(var_output, XfhlNode.OUTPUT));
//		//XfhlModule actual = resultado;
//		//XfhlModule siguiente = resultado;
//		char [] cod_str = codificacion.toCharArray();
//		for (int index = cod_str.length - 1; index >= 0; index--)  {
//			 if (cod_str[index] == '-')  {
//				 if (index != cod_str.length - 1)  {
//					 siguiente = new XfhlModule();
//					 siguiente.setSal(new XfhlNode(var_inner, XfhlNode.INNER));
//					 var_inner++;
//					 if (actual.getEnt1() == null)
//						 actual.setEnt1(siguiente);
//					 else
//						 actual.setEnt2(siguiente);
//				 
//					 // si queda alguna entrada por asignarle se guarda en una lista que se usa como pila 
//					 if ((actual.getEnt1() == null) || (actual.getEnt2() == null))
//						 modulos_pendientes.add(0, actual);
//				 
//					 actual = siguiente;
//				 }
//				 else  {
//					 resultado = new XfhlModule();
//					 resultado.setSal(new XfhlNode(var_output, XfhlNode.OUTPUT));
//					 actual = resultado;
//				 }
//			 }
//			 else  {
//				 XfhlNode nodo = new XfhlNode(Character.getNumericValue(cod_str[index]), XfhlNode.INPUT);
//				 if (actual.getEnt2() == null)
//					 actual.setEnt2(nodo);
//				 else
//					 actual.setEnt1(nodo);
//				 
//				 if ((actual.getEnt1() != null) && (actual.getEnt2() != null))
//					 if (modulos_pendientes.size() > 0)
//						 actual = modulos_pendientes.remove(0);
//			 }
//		}
//		//System.out.println("Resultado decodificacion: " + resultado.getDescripcion());
//		return resultado;
//	}
	
//	@SuppressWarnings("unused")
//	private static XfhlModule decodeBackward(DoubleChromosome codificacion)  {
//		
//		List<XfhlModule> modulos_pendientes = new ArrayList<XfhlModule>();
//		int var_inner = 1, var_output = 1;
//		//XfhlModule resultado = new XfhlModule();
//		XfhlModule resultado = null;
//		XfhlModule actual = null;
//		XfhlModule siguiente = null;
//		//resultado.setSal(new XfhlNode(var_output, XfhlNode.OUTPUT));
//		//XfhlModule actual = resultado;
//		//XfhlModule siguiente = resultado;
//		char [] cod_str = codificacion.getEstructura();
//		for (int index = 0; index < cod_str.length; index++)  {
//			 if (cod_str[index] == '-')  {
//				 if (index != 0)  {
//					 siguiente = new XfhlModule();
//					 siguiente.setSal(new XfhlNode(var_inner, XfhlNode.INNER));
//					 var_inner++;
//					 if (actual.getEnt1() == null)
//						 actual.setEnt1(siguiente);
//					 else
//						 actual.setEnt2(siguiente);
//				 
//					 // si queda alguna entrada por asignarle se guarda en una lista que se usa como pila 
//					 if ((actual.getEnt1() == null) || (actual.getEnt2() == null))
//						 modulos_pendientes.add(0, actual);
//				 
//					 actual = siguiente;
//				 }
//				 else  {
//					 resultado = new XfhlModule();
//					 resultado.setSal(new XfhlNode(var_output, XfhlNode.OUTPUT));
//					 actual = resultado;
//				 }
//			 }
//			 else  {
//				 XfhlNode nodo = new XfhlNode(Character.getNumericValue(cod_str[index]), XfhlNode.INPUT);
//				 if (actual.getEnt2() == null)
//					 actual.setEnt2(nodo);
//				 else
//					 actual.setEnt1(nodo);
//				 
//				 if ((actual.getEnt1() != null) && (actual.getEnt2() != null))
//					 if (modulos_pendientes.size() > 0)
//						 actual = modulos_pendientes.remove(0);
//			 }
//		}
//		//System.out.println("Resultado decodificacion: " + resultado.getDescripcion());
//		return resultado;
//	}
	
	/**
	 * Metodo que codifica un cromosoma a partir de una estructura jerarquica
	 * @param modulo Estructura jerarquica a codificar
	 * @param numVariables Numero de variables de la estructura jerarquica
	 * @return DoubleChromosome Cromosoma resultante de la estructura jerarquica
	 * */
	
	public static DoubleChromosome codeBackwardAnchura(XfhlModule modulo, int numVariables)  {
	
		//System.out.println(modulo.getDescripcion());
		List<String> codificacionList = new ArrayList<String>();
		List<XfhlModule> modulos = new ArrayList<XfhlModule>();
		modulos.add(modulo);
		XfhlModule actual = null;
		XfhlModule auxiliar = null;
		codificacionList.add(EncodingSymbols.startCodification);
		while (modulos.size() > 0)  {
			actual = modulos.remove(0);
			if (actual.getEnt2() instanceof XfhlNode)  {
				Integer entrada = ((XfhlNode) actual.getEnt2()).getValor();
				codificacionList.add(String.valueOf(entrada));
			}
			if (actual.getEnt2() instanceof XfhlModule)  {
				codificacionList.add(EncodingSymbols.moduleSymbolString);
				auxiliar = (XfhlModule) actual.getEnt2();
				modulos.add(auxiliar);
			}
			
			if (actual.getEnt1() instanceof XfhlNode)  {
				Integer entrada = ((XfhlNode) actual.getEnt1()).getValor();
				codificacionList.add(String.valueOf(entrada));
			}
			if (actual.getEnt1() instanceof XfhlModule)  {
				codificacionList.add(EncodingSymbols.moduleSymbolString);
				auxiliar = (XfhlModule) actual.getEnt1();
				modulos.add(auxiliar);
			}
		}
		return new DoubleChromosome(numVariables, codificacionList);
	}
	
//	public static XfhlModule decodeBackwardAnchura(DoubleChromosome cromosoma)  {
//		
//		System.out.println("Decodificacion");
//		XfhlModule modulo = new XfhlModule();
//		modulo.setIdMod((int)RandomNumberGenerator.generate());
//		modulo.setSal(new XfhlNode(0, XfhlNode.OUTPUT));
//		XfhlModule actual = modulo;
//		int [] variables = cromosoma.getVariables();
//		char [] estructura = cromosoma.getEstructura();
//		int posicion = 0, longitudEstructura = cromosoma.getNumModulos() + cromosoma.getNumVariables();
//		char cEstructura = ' ';
//		int numInnerInputs = cromosoma.getNumModulos() - 1;
//		List<XfhlNode> innerInputs = new ArrayList<XfhlNode>();
//		List<XfhlModule> modulos = new ArrayList<XfhlModule>();
//		
//		for (int i = 0; i < numInnerInputs; i++)  {
//			 innerInputs.add(0, new XfhlNode(i, XfhlNode.INNER));
//		}
//		for (int index = 1; index < longitudEstructura; index++)  {
//			 cEstructura = estructura[index];
//			 if (cEstructura == EncodingSymbols.signoVariable)  {
//				 XfhlNode nodo = new XfhlNode(variables[posicion], XfhlNode.INPUT);
//				 posicion++;
//				 if (actual.getEnt2() == null)
//					 actual.setEnt2(nodo);
//				 else  {
//					 if (actual.getEnt1() == null)
//						 actual.setEnt1(nodo);
//				 }
//			 }
//			 if (cEstructura == EncodingSymbols.signoModulo)  {
//				 XfhlModule nuevo = new XfhlModule();
//				 nuevo.setIdMod((int)RandomNumberGenerator.generate());
//				 nuevo.setSal(innerInputs.remove(0));
//				 if (actual.getEnt2() == null)
//					 actual.setEnt2(nuevo);
//				 else  {
//					 if (actual.getEnt1() == null)
//						 actual.setEnt1(nuevo);
//				 }
//				 modulos.add(nuevo);
//				 
//			 }
//			 if (((actual.getEnt1() != null) && (actual.getEnt2() != null)) && (index != longitudEstructura -1)) {
//				 actual = modulos.remove(0);
//			 }
//		}
//		//System.out.println(modulo.getDescripcion());
//		return modulo;
//	}
	
	/**
	 * Metodo que genera una estructura jerarquica a partir de un cromosoma
	 * @param cromosoma Cromosoma a decodificar
	 * @return XfghlModule Estructura jerarquica generada
	 * */
	
	public static synchronized XfhlModule decodeBackwardAnchuraSimple(DoubleChromosome cromosoma)  {
		
		int modCounter = 0;
		XfhlModule modulo = new XfhlModule();
		modulo.setIdMod(modCounter++);
		modulo.setSal(new XfhlNode(0, XfhlNode.OUTPUT));
		XfhlModule actual = modulo;
		int [] variables = cromosoma.getVariables();
		char [] estructura = cromosoma.getEstructura();
		int posicion = 0, longitudEstructura = cromosoma.getNumModulos() + cromosoma.getNumVariables();
		char cEstructura = ' ';
		int numInnerInputs = cromosoma.getNumModulos() - 1;
		List<XfhlNode> innerInputs = new ArrayList<XfhlNode>();
		List<XfhlModule> modulos = new ArrayList<XfhlModule>();
		
		for (int i = 0; i < numInnerInputs; i++)  {
			 innerInputs.add(0, new XfhlNode(i, XfhlNode.INNER));
		}
		for (int index = 1; index < longitudEstructura; index++)  {
			 cEstructura = estructura[index];
			 if (cEstructura == EncodingSymbols.variableSymbol)  {
				 XfhlNode nodo = new XfhlNode(variables[posicion], XfhlNode.INPUT);
				 posicion++;
				 if (actual.getEnt2() == null)
					 actual.setEnt2(nodo);
				 else  {
					 if (actual.getEnt1() == null)
						 actual.setEnt1(nodo);
				 }
			 }
			 if (cEstructura == EncodingSymbols.moduleSymbol)  {
				 XfhlModule nuevo = new XfhlModule();
				 nuevo.setIdMod(modCounter++);
				 nuevo.setSal(innerInputs.remove(0));
				 if (actual.getEnt2() == null)
					 actual.setEnt2(nuevo);
				 else  {
					 if (actual.getEnt1() == null)
						 actual.setEnt1(nuevo);
				 }
				 modulos.add(nuevo);
				 
			 }
			 if (((actual.getEnt1() != null) && (actual.getEnt2() != null)) && (index != longitudEstructura -1)) {
				 actual = modulos.remove(0);
			 }
		}
		//System.out.println(modulo.getDescripcion());
		return modulo;
	}
	
	/**
	 * Metodo que genera la codificacion canonica de un cromosoma
	 * @param cromosoma Cromosoma con la codificacion
	 * @return String Cadena con la codificacion canonica del cromosoma
	 * */
	
	public static String generarCanonico(DoubleChromosome cromosoma)  {
		
		// criterio seguido: las entradas menores iran a la primera entrada
		int menorEntrada1 = -1, menorEntrada2 = -1;
		String canonico = "";
		XfhlModule modulo = Encoder.decodeBackwardAnchuraSimple(cromosoma);
		XfhlModule actual = null;
		XfhlModule auxiliar = null;
		List<XfhlModule> modulos = new ArrayList<XfhlModule>();
		modulos.add(modulo);
		while (modulos.size() > 0)  {
			actual = modulos.remove(0);
			menorEntrada1 = Encoder.getMenorEntrada(actual.getEnt1());
			menorEntrada2 = Encoder.getMenorEntrada(actual.getEnt2());
			if (menorEntrada2 < menorEntrada1)  {
				Encoder.realizarGiro(actual);
			}
			if (actual.getEnt2() instanceof XfhlModule)  {
				auxiliar = (XfhlModule) actual.getEnt2();
				modulos.add(auxiliar);
			}
			if (actual.getEnt1() instanceof XfhlModule)  {
				auxiliar = (XfhlModule) actual.getEnt1();
				modulos.add(auxiliar);
			}
		}
		DoubleChromosome cromosomaCanonico = Encoder.codeBackwardAnchura(modulo, cromosoma.getNumVariables());
		canonico = cromosomaCanonico.getCodificacion();
		return canonico;
	}
	
	/**
	 * Metodo que intercambia las entradas de un modulo
	 * @param modulo Modulo con las entradas a intercambiar
	 * */
	
	private static void realizarGiro(XfhlModule modulo)  {
		
		XfhlINode entrada1 = modulo.getEnt1();
		modulo.setEnt1(modulo.getEnt2());
		modulo.setEnt2(entrada1);
	}
	
	/**
	 * Metodo que calcula la menor entrada de un nodo
	 * @param node Nodo con las entradas a calcular
	 * @return int Valor de la menor entrada del modulo
	 * */
	
	private static int getMenorEntrada(XfhlINode node)  {
		
		int menor = -1;
		if (node instanceof XfhlNode)  {
			XfhlNode nodoEntrada = (XfhlNode) node;
			menor = nodoEntrada.getValor();
		}
		else  {
			XfhlModule modulo = (XfhlModule) node;
			int menorEntrada1 = Encoder.getMenorEntrada(modulo.getEnt1());
			int menorEntrada2 = Encoder.getMenorEntrada(modulo.getEnt2());
			if (menorEntrada1 < menorEntrada2)
				menor = menorEntrada1;
			else
				menor = menorEntrada2;
		}
		return menor;
	}
}
